import itertools
import random
import time

from ultralytics import YOLO
import math
from sklearn.linear_model import LinearRegression
from collections import defaultdict
import numpy as np
import os
import cv2
import json
import logging
import pandas as pd
from shapely.geometry import Polygon, LineString, box

# logging.basicConfig(
#     filename='yolov11-obb_detect.log',
#     level=logging.INFO,
#     format='%(asctime)s - %(levelname)s - %(message)s'
# )
target_labels = [
    'C_JDX', 'C_DX', 'C_DXXJ', 'C_YC', 'C_CLS', 'C_CLSDLJXJ', 'C_CLSDXXJ', 'C_CLSDXXJ_KT', 'C_CLSDXXJ_KTLS',
    'C_CLSZXMJS', 'C_CLSZXMJXJ', 'C_CLSZZDC', 'C_DLJX', 'C_DLJXJ', 'C_JCX', 'C_JCXDLJXJ', 'C_JCXDXXJ',
    'C_JCXZXMJS', 'C_JCXZXMJXJ', 'C_JCXZXMJXJW'
]


# 新增辅助函数
def compute_longest_edge_angle(points):
    """计算四边形最长边的角度"""
    points_np = np.array(points, dtype=np.float32)
    max_length = 0
    long_edge_angle = 0

    for i in range(len(points_np)):
        p1 = points_np[i]
        p2 = points_np[(i + 1) % 4]
        edge_vec = p2 - p1
        length = np.linalg.norm(edge_vec)

        if length > max_length:
            max_length = length
            long_edge_angle = np.degrees(np.arctan2(edge_vec[1], edge_vec[0])) % 180

    return long_edge_angle

    # ---------------------- 几何计算工具函数 ----------------------


def calculate_angle(p1, p2):
    """计算两点连线的角度(0-180度)"""
    dx = p2[0] - p1[0]
    dy = p2[1] - p1[1]
    return math.degrees(math.atan2(dy, dx)) % 180


def calculate_distance(p1, p2):
    """计算两点之间的欧几里得距离"""
    return math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2)


def find_longest_edge(vertices):
    """
    计算四边形最长边的长度
    参数:
    vertices -- 四边形的四个顶点坐标，格式为[(x1,y1), (x2,y2), (x3,y3), (x4,y4)]
    返回:
    最长边的长度
    """
    if len(vertices) != 4:
        raise ValueError("四边形需要恰好四个顶点")

    # 计算四条边的长度
    edges = [
        calculate_distance(vertices[0], vertices[1]),  # 边1: 顶点0到顶点1
        calculate_distance(vertices[1], vertices[2]),  # 边2: 顶点1到顶点2
        calculate_distance(vertices[2], vertices[3]),  # 边3: 顶点2到顶点3
        calculate_distance(vertices[3], vertices[0])  # 边4: 顶点3到顶点0（闭合边）
    ]

    # 找出最长边
    longest_edge = max(edges)
    return longest_edge


def get_longest_sides_angles(points):
    """获取四边形最长两条边的角度平均值"""
    # 计算所有边长
    sides = [
        (0, 1, math.dist(points[0], points[1])),
        (1, 2, math.dist(points[1], points[2])),
        (2, 3, math.dist(points[2], points[3])),
        (3, 0, math.dist(points[3], points[0]))
    ]
    # 按边长排序
    sides.sort(key=lambda x: x[2], reverse=True)
    # 取最长两条边的角度
    angle1 = calculate_angle(points[sides[0][0]], points[sides[0][1]])
    angle2 = calculate_angle(points[sides[1][0]], points[sides[1][1]])
    return (angle1 + angle2) / 2


def get_centroid(points):
    """计算四边形中心点"""
    x = sum(p[0] for p in points) / 4
    y = sum(p[1] for p in points) / 4
    return (x, y)


def group_quadrilaterals(data, angle_threshold=2, centroid_threshold=2):
    """主分组函数
        存在第一个分组符合条件，就不对比后面组别的问题，待修改
    """
    if not data:
        return []
    if len(data) <= 1:
        return [data] if data else []

    groups = [[data[0]]]  # 第一个数据直接进入第一组

    for i in range(1, len(data)):  # 从第一个开始往后的每个数据都和每个组的最后一个数据进行对比，这样可以保证是两两相邻的bbox进行对比
        current = data[i]
        current_angle = get_longest_sides_angles(current['points'])
        current_centroid = get_centroid(current['points'])  # 四边形中心点

        placed = False
        tmp_dict = {}
        # 检查所有现有分组
        for group in groups:
            # 获取组内最后一个四边形作为比较基准
            last_in_group = group[-1]
            last_angle = get_longest_sides_angles(last_in_group['points'])
            last_centroid = get_centroid(last_in_group['points'])

            # 计算角度差
            angle_diff = abs(current_angle - last_angle)
            angle_diff = min(angle_diff, 180 - angle_diff)  # 处理角度环绕

            # 计算中心点连线角度
            centroid_angle = calculate_angle(last_centroid, current_centroid)
            centroid_angle_diff = abs(centroid_angle - last_angle)
            centroid_angle_diff = min(centroid_angle_diff, 180 - centroid_angle_diff)

            # 检查阈值条件
            if angle_diff < angle_threshold and centroid_angle_diff < centroid_threshold:
                group_index = groups.index(group)
                tmp_dict[f"{group_index}"] = centroid_angle_diff
                # group.append(current)
                # placed = True
                # break  # 找到合适的分组，将数据加入组内，并且跳出循环
        if tmp_dict:
            min_v = min(tmp_dict.values())
            for k, v in tmp_dict.items():
                if v == min_v:
                    groups[int(k)].append(current)
                    placed = True
                    break  # 找到合适的分组，将数据加入组内，并且跳出循环
        if not placed:
            groups.append([current])  # 没有找到分组就建立新的分组

    return groups


def get_shortest_edge(points):
    """
    获取四边形最短边的两个顶点坐标
    参数:
        points: 四边形四个顶点的列表，格式为 [(x1,y1), (x2,y2), (x3,y3), (x4,y4)]
    返回:
        最短边的两个顶点坐标 ((xa,ya), (xb,yb))
    """
    if len(points) != 4:
        raise ValueError("必须提供4个顶点坐标")

    # 计算所有可能的边及其长度
    edges = [
        (points[0], points[1]),
        (points[1], points[2]),
        (points[2], points[3]),
        (points[3], points[0])
    ]

    # 计算每条边的长度并找出最短的
    shortest_edge = min(edges,
                        key=lambda edge: math.sqrt((edge[1][0] - edge[0][0]) ** 2 +
                                                   (edge[1][1] - edge[0][1]) ** 2))

    return shortest_edge


def get_shortest_edges(points):
    """
    获取四边形最短的两条边的顶点坐标（端点格式为 [x, y]）
    参数:
        points: 四边形四个顶点的列表，格式为 [(x1,y1), (x2,y2), (x3,y3), (x4,y4)]
    返回:
        最短的两条边的顶点坐标，格式如：
        [
            [[xa1, ya1], [xb1, yb1]],  # 第一条最短边
            [[xa2, ya2], [xb2, yb2]]   # 第二条最短边
        ]
    """
    if len(points) != 4:
        raise ValueError("必须提供4个顶点坐标")

    # 定义所有可能的边（按顺序连接）
    edges = [
        [points[0], points[1]],  # 边1: [p0, p1]
        [points[1], points[2]],  # 边2: [p1, p2]
        [points[2], points[3]],  # 边3: [p2, p3]
        [points[3], points[0]]  # 边4: [p3, p0]
    ]

    # 计算每条边的长度，并存储为 (边, 长度)
    edges_with_length = []
    for edge in edges:
        p1, p2 = edge
        length = math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2)
        edges_with_length.append((edge, length))

    # 按长度排序，并提取最短的两条边
    edges_with_length.sort(key=lambda x: x[1])  # 按边长升序排序
    shortest_edges = [edge for edge, length in edges_with_length[:2]]

    # 确保每个端点是列表 [x, y] 格式（如果输入是元组）
    formatted_edges = []
    for edge in shortest_edges:
        p1, p2 = edge
        formatted_edge = [
            [float(p1[0]), float(p1[1])],  # 转换为 [x, y] 列表
            [float(p2[0]), float(p2[1])]
        ]
        formatted_edges.append(formatted_edge)

    return formatted_edges


def line_intersection(line1, line2):
    """
    计算无限长直线（line1）与有限线段（line2）的交点
    - line1: 无限长直线，由两个端点定义 [(x1, y1), (x2, y2)]，向两端无限延伸
    - line2: 有限线段，由两个端点定义 [(x3, y3), (x4, y4)]，仅在两点之间有效
    返回：
        交点坐标 (x, y)，若不相交则返回 None
    """
    x1, y1 = line1[0]  # 直线上的点1
    x2, y2 = line1[1]  # 直线上的点2（直线向两点外侧无限延伸）
    x3, y3 = line2[0]  # 线段端点1
    x4, y4 = line2[1]  # 线段端点2

    # 计算分母（判断直线与线段所在直线是否平行）
    denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
    if denom == 0:
        return None  # 平行或共线，无交点

    # 计算参数 ua（直线line1上的参数，无范围限制，因直线无限长）
    # 和 ub（线段line2上的参数，需在0-1之间才有效）
    ua_numerator = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
    ua = ua_numerator / denom
    ub_numerator = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
    ub = ub_numerator / denom

    # 判断交点是否在线段line2上（0 ≤ ub ≤ 1）
    # 直线line1无限长，无需限制ua范围
    if 0 <= ub <= 1:
        x = x1 + ua * (x2 - x1)
        y = y1 + ua * (y2 - y1)
        return (x, y)
    else:
        return None  # 交点不在line2线段范围内


def get_longest_edges_intersections(quad_points, img_width, img_height):
    """
    获取四边形最长两条边与图像边界的交点
    参数:
        quad_points: 四边形顶点列表 [(x1,y1), (x2,y2), (x3,y3), (x4,y4)]
        img_width: 图像宽度
        img_height: 图像高度
    返回:
        四个交点坐标 [(x1,y1), (x2,y2), (x3,y3), (x4,y4)]
        (如果边与边界无交点，对应位置为None)
    """
    # 计算所有边的长度
    edges = [(quad_points[i], quad_points[(i + 1) % 4]) for i in range(4)]
    lengths = [np.linalg.norm(np.array(e[1]) - np.array(e[0])) for e in edges]

    # 找出两条最长的边（可能有多条长度相同）
    sorted_indices = np.argsort(lengths)[::-1]
    longest_edges = [edges[sorted_indices[0]], edges[sorted_indices[1]]]

    # 定义图像边界
    borders = [
        [(0, 0), (img_width, 0)],  # 上边界
        [(img_width, 0), (img_width, img_height)],  # 右边界
        [(0, img_height), (img_width, img_height)],  # 下边界
        [(0, 0), (0, img_height)]  # 左边界
    ]

    # 计算每条最长边与图像边界的交点
    all_intersections = []
    for edge in longest_edges:
        edge_intersections = []
        for border in borders:
            point = line_intersection(edge, border)
            if point:
                x, y = point
                # 检查交点是否在边界范围内
                if (border[0][0] == border[1][0]):  # 垂直边界
                    if min(border[0][1], border[1][1]) <= y <= max(border[0][1], border[1][1]):
                        edge_intersections.append(point)
                else:  # 水平边界
                    if min(border[0][0], border[1][0]) <= x <= max(border[0][0], border[1][0]):
                        edge_intersections.append(point)

        # 每条边最多保留2个交点（按参数顺序）
        if len(edge_intersections) >= 2:
            # 按参数t值排序
            t_values = [np.dot(np.array(p) - np.array(edge[0]),
                               np.array(edge[1]) - np.array(edge[0])) /
                        np.linalg.norm(np.array(edge[1]) - np.array(edge[0])) ** 2
                        for p in edge_intersections]
            edge_intersections = [p for _, p in sorted(zip(t_values, edge_intersections))]
            all_intersections.extend(edge_intersections[:2])
        else:
            all_intersections.extend(edge_intersections)

    # 补足4个交点（不足的用None填充）
    while len(all_intersections) < 4:
        # print('不足四个点')
        all_intersections.append(None)
    # print(all_intersections)
    return all_intersections[:4]


def compute_convex_quadrilateral_area(points):
    """
    判断四个点是否能构成凸四边形，并计算其面积（如果不能则返回0）
    """
    if len(points) != 4:
        return 0

    # 检查是否构成凸四边形（所有点必须在凸包上）
    hull = convex_hull(points)
    if len(hull) != 4:
        return 0  # 不能构成凸四边形

    # 计算面积（鞋带公式）
    area = 0.0
    n = len(hull)
    for i in range(n):
        x_i, y_i = hull[i]
        x_j, y_j = hull[(i + 1) % n]
        area += (x_i * y_j) - (x_j * y_i)
    return abs(area) / 2


def convex_hull(points):
    """
    计算凸包（Andrew's monotone chain algorithm）
    返回按逆时针排列的凸包点
    """
    points = sorted(points)
    lower = []
    for p in points:
        while len(lower) >= 2 and cross(lower[-2], lower[-1], p) <= 0:
            lower.pop()
        lower.append(p)
    upper = []
    for p in reversed(points):
        while len(upper) >= 2 and cross(upper[-2], upper[-1], p) <= 0:
            upper.pop()
        upper.append(p)
    return lower[:-1] + upper[:-1]


def cross(o, a, b):
    """
    向量叉积 (a - o) × (b - o)
    """
    return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])


def find_max_area_convex_quad(edges1, edges2):
    """
    从两个四边形的最短边中各选一条边，组合成凸四边形，返回面积最大的情况
    参数:
        edges1: 第一个四边形的最短边列表，格式如 [[[x1,y1], [x2,y2]], ...]
        edges2: 第二个四边形的最短边列表，格式同上
    返回:
        面积最大的凸四边形的四个顶点，格式 [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]
        如果没有可行解，返回 None
    """
    max_area = 0
    best_quad = None

    # 遍历所有选边组合（edges1选1条，edges2选1条）
    for e1 in edges1:
        for e2 in edges2:
            # 四个点（可能重复或共线）
            p1, p2 = e1
            p3, p4 = e2
            points = [p1, p2, p3, p4]

            # 检查所有排列组合（4! = 24种）
            # print(points)
            for quad in itertools.permutations(points):
                quad = list(quad)
                area = compute_convex_quadrilateral_area(quad)
                if area > max_area:
                    max_area = area
                    best_quad = quad.copy()

    return best_quad if max_area > 0 else None


def fix_missing_boxes(shapes, img_height, img_width, is_dx, is_iou, max_gap=0, angle_threshold=5):
    """改进的补全函数（带方向一致性检查）"""
    class_groups = {}
    divided_data = []
    dx_data = []
    tmp_dx_data = []
    # gj_data = []
    clsdxxj_data = []
    clsdxxjkt_data = []
    clsdxxjktls_data = []
    jcxdxxj_data = []
    cls_data = []
    jcx_data = []
    cls_groups = []
    jcx_groups = []
    for shape in shapes:
        if shape['label'] not in ['C_CLS', 'C_JCX', 'C_DX', 'C_GJ',
                                  'C_CLSDXXJ', 'C_CLSDXXJ_KT', 'C_CLSDXXJ_KTLS', 'C_JCXDXXJ']:  #
            continue
        if shape['label'] == 'C_DX':
            dx_data.append(shape)
        # if shape['label'] == 'C_GJ':
        #     gj_data.append(shape['points'])
        if shape['label'] == 'C_CLSDXXJ':
            clsdxxj_data.append(shape['points'])
        if shape['label'] == 'C_CLSDXXJ_KT':
            clsdxxjkt_data.append(shape['points'])
        if shape['label'] == 'C_CLSDXXJ_KTLS':
            clsdxxjktls_data.append(shape['points'])
        if shape['label'] == 'C_JCXDXXJ':
            jcxdxxj_data.append(shape['points'])
        if shape['label'] not in class_groups:
            class_groups[shape['label']] = []
        class_groups[shape['label']].append(shape)
    # print(f"class_groups={class_groups}")   # 得到需要分组的数据
    #  第四种分组方法
    if 'C_CLS' in class_groups:
        # divided_data.append(group_quadrilaterals(class_groups['C_CLS']))
        #  过滤分组之后散落的单个目标物
        for data in group_quadrilaterals(class_groups['C_CLS']):
            if len(data) < 2:
                continue
            cls_groups.append(data)
        if len(cls_groups):
            divided_data.append(cls_groups)
    if 'C_JCX' in class_groups:
        # divided_data.append(group_quadrilaterals(class_groups['C_JCX']))
        for data in group_quadrilaterals(class_groups['C_JCX']):
            if len(data) < 2:
                continue
            jcx_groups.append(data)
        if len(jcx_groups):
            divided_data.append(jcx_groups)
    # 打印分组结果
    # for i, group in enumerate(groups):
    #     print(f"第{i + 1}组包含{len(group)}个四边形")
    if is_dx:
        fixed_shapes = [s for s in shapes if s['label'] not in ['C_CLS', 'C_JCX', "C_DX"]]  #
    else:
        fixed_shapes = [s for s in shapes if s['label'] not in ['C_CLS', 'C_JCX']]

    for label_data in divided_data:
        for group_data in label_data:
            # 判断组内数据长度
            if len(group_data) < 2:
                fixed_shapes.append(group_data[0])
            else:
                centers = [np.mean(np.array(b['points']), axis=0) for b in group_data]
                sorted_indices = np.argsort([c[0] for c in centers])
                sorted_boxes = [group_data[i] for i in sorted_indices]
                #  排序好的数据直接生成一个四边形
                data_begin, data_end = sorted_boxes[0], sorted_boxes[-1]
                #  直接取边不可用，建议改用最短边
                #  get_shortest_edges返回的是最短两条边
                #  get_shortest_edge返回的是最短边
                begin_short_edge = get_shortest_edges(data_begin['points'])
                end_short_edge = get_shortest_edges(data_end['points'])
                # 新增的找最短边组合成的最大面积的四个点坐标
                quad_points = find_max_area_convex_quad(begin_short_edge, end_short_edge)
                # left_edge = (begin_short_edge[0], begin_short_edge[1])
                # right_edge = (end_short_edge[0], end_short_edge[1])
                # quad_points = [left_edge[0], left_edge[1], right_edge[0], right_edge[1]]

                quad_points = order_points(quad_points)
                intersections = get_longest_edges_intersections(quad_points, img_width, img_height)
                # print(intersections)
                converted_data = [[x, y] for x, y in intersections]
                fixed_shapes.append({
                    "label": group_data[0]['label'],
                    "points": converted_data,
                    "group_id": group_data[0]['group_id'],
                    "shape_type": "polygon",
                    "flags": {}  # 标记为补全框
                })
    # 开始补全DX

    jcx_dict_data = {}
    cls_dict_data = {}
    index_list = []
    for json_data in fixed_shapes:
        if json_data['label'] == 'C_JCX':
            json_data_index = fixed_shapes.index(json_data)
            jcx_dict_data[str(json_data_index)] = json_data
            jcx_data.append(json_data['points'])
        if json_data['label'] == 'C_CLS':
            json_data_index = fixed_shapes.index(json_data)
            cls_dict_data[str(json_data_index)] = json_data
            cls_data.append(json_data['points'])
    # 删除jcx和cls重叠
    for jcx_key, jcx_value in jcx_dict_data.items():
        # print(jcx_key, jcx_value)
        if jcx_key in index_list:
            continue

        for cls_key, cls_value in cls_dict_data.items():
            # print(cls_key, cls_value)

            if cls_key in index_list:
                continue
            # 计算jcx和cls的iou
            iou = quadrilateral_iou(jcx_value['points'], cls_value['points'])
            # print(iou)
            # iou 值达到条件 谁多去除谁
            if iou > 0.4 and is_iou:
                if len(jcx_dict_data) > len(cls_dict_data):
                    index_list.append(jcx_key)
                else:
                    index_list.append(cls_key)
            #  获取最长两条边的方向向量
            jcx_angle = get_longest_sides_angles(jcx_value['points'])
            jcx_center = get_centroid(jcx_value['points'])
            jcx_longedge = find_longest_edge(jcx_value['points'])
            cls_angle = get_longest_sides_angles(cls_value['points'])
            cls_center = get_centroid(cls_value['points'])
            cls_longedge = find_longest_edge(cls_value['points'])
            # print(jcx_longedge, cls_longedge)
            # 计算角度差
            angle_diff = abs(jcx_angle - cls_angle)
            angle_diff = min(angle_diff, 180 - angle_diff)  # 处理角度环绕

            # 计算中心点连线角度
            centroid_angle = calculate_angle(jcx_center, cls_center)
            centroid_angle_diff = abs(centroid_angle - cls_angle)
            centroid_angle_diff = min(centroid_angle_diff, 180 - centroid_angle_diff)
            # print(angle_diff, centroid_angle_diff)
            if angle_diff < 2 and centroid_angle_diff < 2:
                if jcx_longedge > cls_longedge + 400:
                    #  保留jcx  去除cls
                    index_list.append(cls_key)
                if cls_longedge > jcx_longedge + 400:
                    #  保留cls  去除jcx
                    index_list.append(jcx_key)
                # if abs(cls_longedge - jcx_longedge) < 10:
                #     print(1111111)
                #     index_list.append(jcx_longedge)
    # 删除cls 重叠
    # print(cls_dict_data)
    cls_items = list(cls_dict_data.items())
    # print(len(cls_dict_data))
    if len(cls_items) > 1:
        for i in range(len(cls_items)):
            cls_key1, cls_value1 = cls_items[i]
            for j in range(i + 1, len(cls_items)):
                if cls_key1 in index_list:
                    continue
                cls_key2, cls_value2 = cls_items[j]
                iou = quadrilateral_iou(cls_value1['points'], cls_value2['points'])
                if iou > 0.4 and is_iou:
                    print(cls_key1, cls_key2, iou)
                    index_list.append(cls_key1)
    # 删除jcx重叠
    jcx_items = list(jcx_dict_data.items())
    if len(jcx_items) > 1:
        for i in range(len(jcx_items)):
            jcx_key1, jcx_value1 = jcx_items[i]
            if jcx_key1 in index_list:
                continue
            for j in range(i + 1, len(jcx_items)):
                jcx_key2, jcx_value2 = jcx_items[j]
                iou = quadrilateral_iou(jcx_value1['points'], jcx_value2['points'])
                if iou > 0.4 and is_iou:
                    index_list.append(jcx_key1)

    #  # 删除无用数据  从后面开始删除
    index_list = list(set(index_list))
    for index in sorted(index_list, reverse=True):
        if 0 <= int(index) < len(fixed_shapes):  # 检查索引是否有效
            del fixed_shapes[int(index)]

    #  删除之后的数据中去除 jcx和cls
    if is_dx:
        for json_data in fixed_shapes:
            if json_data['label'] == 'C_JCX':
                jcx_data.append(json_data['points'])
            if json_data['label'] == 'C_CLS':
                cls_data.append(json_data['points'])
        if not len(dx_data):
            print("没有dx数据")
            return fixed_shapes
        # 只有一个DX数据
        # print(dx_data)
        if len(dx_data) > 1:
            # 判断能不能合并，能合并的合并成一个bbox，不能合并的单独存在
            dx_group = group_quadrilaterals(dx_data, angle_threshold=8.0, centroid_threshold=8.0)
            # print(dx_group)
            for group_data in dx_group:
                # for group_data in label_data:
                # 判断组内数据长度
                if len(group_data) < 2:
                    tmp_dx_data.append(group_data[0])
                else:
                    centers = [np.mean(np.array(b['points']), axis=0) for b in group_data]
                    sorted_indices = np.argsort([c[0] for c in centers])
                    sorted_boxes = [group_data[i] for i in sorted_indices]
                    #  排序好的数据直接生成一个四边形
                    data_begin, data_end = sorted_boxes[0], sorted_boxes[-1]
                    #  直接取边不可用，建议改用最短边
                    begin_short_edge = get_shortest_edges(data_begin['points'])
                    end_short_edge = get_shortest_edges(data_end['points'])
                    quad_points = find_max_area_convex_quad(begin_short_edge, end_short_edge)

                    # left_edge = (begin_short_edge[0], begin_short_edge[1])
                    # right_edge = (end_short_edge[0], end_short_edge[1])
                    # quad_points = [left_edge[0], left_edge[1], right_edge[0], right_edge[1]]

                    quad_points = order_points(quad_points)
                    quad_points = [arr.tolist() for arr in quad_points]
                    #  dx 不需要计算和图像边界的交点
                    # intersections = get_longest_edges_intersections(quad_points, img_width, img_height)
                    # print(intersections)
                    # converted_data = [[x, y] for x, y in intersections]
                    tmp_dx_data.append({
                        "label": group_data[0]['label'],
                        "points": quad_points,
                        "group_id": group_data[0]['group_id'],
                        "shape_type": "polygon",
                        "flags": {}  # 标记为补全框
                    })
            dx_data = tmp_dx_data
        # 开始处理每一条dx数据,每天dx数据都只有一个四边形
        # print("dx数据：：", dx_data)
        for dx in dx_data:
            #  dx两端都和gj有iou or 一端和gj有iou,另一端靠近边界就不需要补全
            #  获取上端坐标和下端坐标
            up_down = find_shortest_edges_and_classify(dx['points'])
            # print(up_down)
            upper_has_iou1 = check_points_in_quadrilaterals(up_down['up'], clsdxxj_data)
            upper_has_iou2 = check_points_in_quadrilaterals(up_down['up'], clsdxxjkt_data)
            upper_has_iou3 = check_points_in_quadrilaterals(up_down['up'], clsdxxjktls_data)
            if upper_has_iou3 or upper_has_iou2 or upper_has_iou1:
                upper_has_iou = True
            else:
                upper_has_iou = False
            lower_has_iou = check_points_in_quadrilaterals(up_down['down'], jcxdxxj_data)
            # print(upper_has_iou, lower_has_iou)
            #  不需要补全
            if upper_has_iou and lower_has_iou:
                print("两端都和构件有交集")
                dx_new_points = order_points(dx['points'])
                dx_new_points = [arr.tolist() for arr in dx_new_points]
                fixed_shapes.append({
                    "label": 'C_DX',
                    "points": dx_new_points,
                    "group_id": dx['group_id'],
                    "shape_type": "polygon",
                    "flags": {}  # 标记为补全框
                })
                continue
            #  检查下边点是否需要补全
            if upper_has_iou and not lower_has_iou:
                #  下边靠近边界不需要补全
                if is_edge_near_boundary(up_down['down'], img_width, img_height):
                    print("上端有交集，下端靠近边界")
                    dx_new_points = order_points(dx['points'])
                    dx_new_points = [arr.tolist() for arr in dx_new_points]
                    fixed_shapes.append({
                        "label": 'C_DX',
                        "points": dx_new_points,
                        "group_id": dx['group_id'],
                        "shape_type": "polygon",
                        "flags": {}  # 标记为补全框
                    })
                    continue
                #  下边不靠近边界，再判断（现在直接补全到gj）
                else:
                    print("开始判断能否补全到gj")
                    dx_new_points, first_gj_points = extend_along_longest_edge(dx['points'], jcxdxxj_data,
                                                                               extend_direction='down')
                    if first_gj_points is None:
                        #  jcx数据需要提取出来！！！！！
                        print("下方没有找到合适的构件进行补全")
                        dx_new_points, first_jcx_points = extend_along_longest_edge(dx['points'], jcx_data,
                                                                                    extend_direction='down')
                        if first_jcx_points is None:
                            ## 向下延伸到边界
                            print("下方没有找到合适的接触线进行补全")
                            dx_new_points = extend_to_image_boundary(dx['points'], img_width, img_height,
                                                                     extend_down=True)
                            dx_new_points = order_points(dx_new_points)
                            dx_new_points = [arr.tolist() for arr in dx_new_points]
                            fixed_shapes.append({
                                "label": 'C_DX',
                                "points": dx_new_points,
                                "group_id": dx['group_id'],
                                "shape_type": "polygon",
                                "flags": {}  # 标记为补全框
                            })
                            continue
                        # 补全到接触线
                        dx_new_points = order_points(dx_new_points)
                        dx_new_points = [arr.tolist() for arr in dx_new_points]
                        fixed_shapes.append({
                            "label": 'C_DX',
                            "points": dx_new_points,
                            "group_id": dx['group_id'],
                            "shape_type": "polygon",
                            "flags": {}  # 标记为补全框
                        })
                        continue
                    #  补全到构件
                    dx_new_points = order_points(dx_new_points)
                    dx_new_points = [arr.tolist() for arr in dx_new_points]
                    fixed_shapes.append({
                        "label": 'C_DX',
                        "points": dx_new_points,
                        "group_id": dx['group_id'],
                        "shape_type": "polygon",
                        "flags": {}  # 标记为补全框
                    })
            #  检查上边点是否需要补全
            if lower_has_iou and not upper_has_iou:
                if is_edge_near_boundary(up_down['up'], img_width, img_height):
                    dx_new_points = order_points(dx['points'])
                    dx_new_points = [arr.tolist() for arr in dx_new_points]
                    fixed_shapes.append({
                        "label": 'C_DX',
                        "points": dx_new_points,
                        "group_id": dx['group_id'],
                        "shape_type": "polygon",
                        "flags": {}  # 标记为补全框
                    })
                    print("下端有交集，上端靠近边界")
                    continue
                else:
                    print("开始向上判断能否补全到clsdxxjktls")
                    dx_new_points, first_gj_points = extend_along_longest_edge(dx['points'], clsdxxjktls_data,
                                                                               extend_direction='up')
                    if first_gj_points is None:
                        print("开始向上判断能否补全到clsdxxjkt")
                        dx_new_points, first_gj_points2 = extend_along_longest_edge(dx['points'], clsdxxjkt_data,
                                                                                    extend_direction='up')
                        if first_gj_points2 is None:
                            print("开始向上判断能否补全到clsdxxj")
                            dx_new_points, first_gj_points3 = extend_along_longest_edge(dx['points'], clsdxxj_data,
                                                                                        extend_direction='up')
                            if first_gj_points3 is None:
                                #  jcx数据需要提取出来！！！！！
                                print("上方没有找到合适的构件进行补全，开始不全到cls")
                                dx_new_points, first_jcx_points4 = extend_along_longest_edge(dx['points'], cls_data,
                                                                                             extend_direction='up')
                                if first_jcx_points4 is None:
                                    ## 向下延伸到边界
                                    print("上方没有找到合适的接触线进行补全,补全到边界")
                                    dx_new_points = extend_to_image_boundary(dx['points'], img_width, img_height,
                                                                             extend_down=False)
                                    dx_new_points = order_points(dx_new_points)
                                    dx_new_points = [arr.tolist() for arr in dx_new_points]
                                    fixed_shapes.append({
                                        "label": 'C_DX',
                                        "points": dx_new_points,
                                        "group_id": dx['group_id'],
                                        "shape_type": "polygon",
                                        "flags": {}  # 标记为补全框
                                    })
                                    continue
                                # 补全到接触线
                                dx_new_points = order_points(dx_new_points)
                                dx_new_points = [arr.tolist() for arr in dx_new_points]
                                fixed_shapes.append({
                                    "label": 'C_DX',
                                    "points": dx_new_points,
                                    "group_id": dx['group_id'],
                                    "shape_type": "polygon",
                                    "flags": {}  # 标记为补全框
                                })
                                continue
                            # 补全到clsdxxj
                            dx_new_points = order_points(dx_new_points)
                            dx_new_points = [arr.tolist() for arr in dx_new_points]
                            fixed_shapes.append({
                                "label": 'C_DX',
                                "points": dx_new_points,
                                "group_id": dx['group_id'],
                                "shape_type": "polygon",
                                "flags": {}  # 标记为补全框
                            })
                            continue
                        # 补全到clsdxxjkt
                        dx_new_points = order_points(dx_new_points)
                        dx_new_points = [arr.tolist() for arr in dx_new_points]
                        fixed_shapes.append({
                            "label": 'C_DX',
                            "points": dx_new_points,
                            "group_id": dx['group_id'],
                            "shape_type": "polygon",
                            "flags": {}  # 标记为补全框
                        })
                        continue
                    #  补全到clsdxxjkt
                    dx_new_points = order_points(dx_new_points)
                    dx_new_points = [arr.tolist() for arr in dx_new_points]
                    fixed_shapes.append({
                        "label": 'C_DX',
                        "points": dx_new_points,
                        "group_id": dx['group_id'],
                        "shape_type": "polygon",
                        "flags": {}  # 标记为补全框
                    })
            #  两端都没有和构件相交
            else:
                #  先补全上端
                up_dx_points = []
                down_dx_points = []
                print("两端都没有与构件相交，开始向上补全")
                up_gj_new_points, first_gj_points = extend_along_longest_edge(dx['points'], clsdxxjktls_data,
                                                                              extend_direction='up')
                if first_gj_points is None:
                    print("上端都没有与clsdxxjktls相交，开始与clsdxxjkt补全")
                    up_gj_new_points = []
                    up_gj_new_points2, first_gj_points2 = extend_along_longest_edge(dx['points'], clsdxxjkt_data,
                                                                                    extend_direction='up')
                    if first_gj_points2 is None:
                        print("上端都没有与clsdxxjkt相交，开始与clsdxxj补全")
                        up_gj_new_points2 = []
                        up_gj_new_points3, first_gj_points3 = extend_along_longest_edge(dx['points'], clsdxxj_data,
                                                                                        extend_direction='up')
                        if first_gj_points3 is None:
                            up_gj_new_points3 = []
                            #  jcx数据需要提取出来！！！！！
                            print("上方没有找到合适的构件进行补全")
                            up_jcx_new_points, first_cls_points = extend_along_longest_edge(dx['points'], cls_data,
                                                                                            extend_direction='up')
                            if first_cls_points is None:
                                up_jcx_new_points = []
                                ## 向下延伸到边界
                                print("上方没有找到合适的承力索进行补全")
                                up_bj_new_points = extend_to_image_boundary(dx['points'], img_width, img_height,
                                                                            extend_down=False)
                                up_bj_new_points = order_points(up_bj_new_points)
                                up_bj_new_points = [arr.tolist() for arr in up_bj_new_points]
                                up_dx_points.append(up_bj_new_points)
                            # 补全到cls
                            if len(up_jcx_new_points):
                                up_jcx_new_points = order_points(up_jcx_new_points)
                                up_jcx_new_points = [arr.tolist() for arr in up_jcx_new_points]
                                up_dx_points.append(up_jcx_new_points)
                        # 补全到clsdxxj
                        if len(up_gj_new_points3):
                            up_gj_new_points3 = order_points(up_gj_new_points3)
                            up_gj_new_points3 = [arr.tolist() for arr in up_gj_new_points3]
                            up_dx_points.append(up_gj_new_points3)
                    # 补全到clsdxxjkt
                    if len(up_gj_new_points2):
                        up_gj_new_points2 = order_points(up_gj_new_points2)
                        up_gj_new_points2 = [arr.tolist() for arr in up_gj_new_points2]
                        up_dx_points.append(up_gj_new_points2)
                # 补全到clsdxxjktls
                if len(up_gj_new_points):
                    up_gj_new_points = order_points(up_gj_new_points)
                    up_gj_new_points = [arr.tolist() for arr in up_gj_new_points]
                    up_dx_points.append(up_gj_new_points)

                down_gj_new_points, first_gj_points = extend_along_longest_edge(dx['points'], jcxdxxj_data,
                                                                                extend_direction='down')
                if first_gj_points is None:
                    down_gj_new_points = []
                    #  jcx数据需要提取出来！！！！！
                    print("下方没有找到合适的构件进行补全")
                    down_jcx_new_points, first_jcx_points = extend_along_longest_edge(dx['points'], jcx_data,
                                                                                      extend_direction='down')
                    if first_jcx_points is None:
                        down_jcx_new_points = []
                        ## 向下延伸到边界
                        print("下面没有找到合适的接触线进行补全")
                        down_bj_new_points = extend_to_image_boundary(dx['points'], img_width, img_height,
                                                                      extend_down=True)
                        down_bj_new_points = order_points(down_bj_new_points)
                        down_bj_new_points = [arr.tolist() for arr in down_bj_new_points]
                        down_dx_points.append(down_bj_new_points)
                    # 补全到接触线
                    if len(down_jcx_new_points):
                        down_jcx_new_points = order_points(down_jcx_new_points)
                        down_jcx_new_points = [arr.tolist() for arr in down_jcx_new_points]
                        down_dx_points.append(down_jcx_new_points)
                #  补全到构件
                if len(down_gj_new_points):
                    down_gj_new_points = order_points(down_gj_new_points)
                    down_gj_new_points = [arr.tolist() for arr in down_gj_new_points]
                    down_dx_points.append(down_gj_new_points)

                begin_short_edge = get_shortest_edges(down_dx_points[0])
                end_short_edge = get_shortest_edges(up_dx_points[0])
                quad_points = find_max_area_convex_quad(begin_short_edge, end_short_edge)

                quad_points = order_points(quad_points)
                quad_points = [arr.tolist() for arr in quad_points]

                #  dx 不需要计算和图像边界的交点
                # intersections = get_longest_edges_intersections(quad_points, img_width, img_height)
                # converted_data = [[x, y] for x, y in intersections]
                fixed_shapes.append({
                    "label": 'C_DX',
                    "points": quad_points,
                    "group_id": dx['group_id'],
                    "shape_type": "polygon",
                    "flags": {}  # 标记为补全框
                })

    return fixed_shapes


def polygon_area(polygon):
    """
    用鞋带公式计算多边形面积（适用于任意简单多边形）
    :param polygon: 多边形顶点列表，格式为 [(x1,y1), (x2,y2), ..., (xn,yn)]，顶点需按顺时针/逆时针排序
    :return: 多边形面积（浮点数）
    """
    n = len(polygon)
    if n < 3:
        return 0.0  # 少于3个顶点，面积为0
    area = 0.0
    for i in range(n):
        x1, y1 = polygon[i]
        x2, y2 = polygon[(i + 1) % n]  # 最后一个点与第一个点连接
        area += (x1 * y2 - x2 * y1)
    return abs(area) / 2.0


def point_in_polygon(point, polygon):
    """
    用射线法判断点是否在多边形内部
    :param point: 待判断点 (x, y)
    :param polygon: 多边形顶点列表 [(x1,y1), ..., (xn,yn)]
    :return: True（在内部）/ False（在外部）
    """
    x, y = point
    n = len(polygon)
    inside = False
    for i in range(n):
        j = (i + 1) % n
        xi, yi = polygon[i]
        xj, yj = polygon[j]

        # 检查点是否在边的上方/下方，以及射线是否与边相交
        if ((yi > y) != (yj > y)):
            # 计算射线与边的交点x坐标
            x_intersect = ((y - yi) * (xj - xi)) / (yj - yi + 1e-8) + xi  # 加1e-8避免除零
            if x < x_intersect:
                inside = not inside  # 穿过边时，内外状态翻转
    return inside


def segment_intersection(a1, a2, b1, b2):
    """
    计算两条线段的交点（若相交）
    :param a1, a2: 线段a的两个端点 (x,y)
    :param b1, b2: 线段b的两个端点 (x,y)
    :return: 交点坐标 (x,y)，不相交则返回None
    """
    x1, y1 = a1
    x2, y2 = a2
    x3, y3 = b1
    x4, y4 = b2

    # 计算分母（判断是否平行）
    denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
    if denom == 0:
        return None  # 线段平行或共线，无交点

    # 计算参数t和u（判断交点是否在线段上）
    t_num = (x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)
    u_num = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3))
    t = t_num / denom
    u = u_num / denom

    # 交点在线段a和b上时，返回交点
    if 0 <= t <= 1 and 0 <= u <= 1:
        x = x1 + t * (x2 - x1)
        y = y1 + t * (y2 - y1)
        return (round(x, 6), round(y, 6))  # 四舍五入避免浮点数误差
    return None


def polygon_intersection(poly1, poly2):
    """
    计算两个多边形的交集区域（返回交集多边形的顶点）
    :param poly1: 第一个多边形顶点列表
    :param poly2: 第二个多边形顶点列表
    :return: 交集多边形的顶点列表（按顺序排列）
    """
    intersection_vertices = []

    # 1. 收集poly1中在poly2内部的顶点
    for p in poly1:
        if point_in_polygon(p, poly2):
            intersection_vertices.append(p)

    # 2. 收集poly2中在poly1内部的顶点
    for p in poly2:
        if point_in_polygon(p, poly1):
            intersection_vertices.append(p)

    # 3. 收集poly1和poly2的边的所有交点
    n1, n2 = len(poly1), len(poly2)
    for i in range(n1):
        a1, a2 = poly1[i], poly1[(i + 1) % n1]  # poly1的一条边
        for j in range(n2):
            b1, b2 = poly2[j], poly2[(j + 1) % n2]  # poly2的一条边
            intersect = segment_intersection(a1, a2, b1, b2)
            if intersect:
                intersection_vertices.append(intersect)

    # 去重（处理重复的交点/顶点）
    unique_vertices = []
    seen = set()
    for p in intersection_vertices:
        key = (p[0], p[1])  # 用元组去重
        if key not in seen:
            seen.add(key)
            unique_vertices.append(p)

    # 对顶点按角度排序（确保是顺时针/逆时针顺序，用于计算面积）
    if len(unique_vertices) < 3:
        return []  # 少于3个顶点，无有效交集
    # 计算中心点
    cx = sum(p[0] for p in unique_vertices) / len(unique_vertices)
    cy = sum(p[1] for p in unique_vertices) / len(unique_vertices)
    # 按相对于中心点的角度排序
    unique_vertices.sort(key=lambda p: math.atan2(p[1] - cy, p[0] - cx))

    return unique_vertices


def quadrilateral_iou(quad1, quad2):
    """
    计算两个四边形的交并比（IoU）
    :param quad1: 第一个四边形的四点坐标，格式 [(x1,y1), (x2,y2), (x3,y3), (x4,y4)]
    :param quad2: 第二个四边形的四点坐标，格式同上
    :return: IoU值（浮点数，范围[0,1]）
    """
    # 计算两个四边形的面积
    area1 = polygon_area(quad1)
    area2 = polygon_area(quad2)
    if area1 == 0 or area2 == 0:
        return 0.0  # 其中一个面积为0，IoU为0

    # 计算交集面积
    intersect_poly = polygon_intersection(quad1, quad2)
    intersect_area = polygon_area(intersect_poly)

    # 计算并集面积
    union_area = area1 + area2 - intersect_area
    if union_area == 0:
        return 0.0

    return intersect_area / union_area


def is_point_in_polygon(point, quadrilateral):
    """
    使用向量叉积法在图像坐标系中判断点是否在四边形内

    Args:
        point: (x, y) 点坐标
        quadrilateral: [(x1,y1), (x2,y2), (x3,y3), (x4,y4)] 四边形的四个顶点坐标

    Returns:
        True: 点在四边形内
        False: 点不在四边形内
    """
    # 确保四边形是封闭的
    if len(quadrilateral) != 4:
        raise ValueError("四边形必须有四个顶点")

    # 获取四边形的四个顶点
    p1, p2, p3, p4 = quadrilateral

    # 计算点与四个顶点形成的向量
    v1 = (p1[0] - point[0], p1[1] - point[1])
    v2 = (p2[0] - point[0], p2[1] - point[1])
    v3 = (p3[0] - point[0], p3[1] - point[1])
    v4 = (p4[0] - point[0], p4[1] - point[1])

    # 计算相邻向量的叉积
    cross1 = v1[0] * v2[1] - v1[1] * v2[0]
    cross2 = v2[0] * v3[1] - v2[1] * v3[0]
    cross3 = v3[0] * v4[1] - v3[1] * v4[0]
    cross4 = v4[0] * v1[1] - v4[1] * v1[0]

    # 如果所有叉积的符号相同（都为正或都为负），则点在四边形内
    # 由于图像坐标系中y轴向下为正，所以叉积的符号判断与笛卡尔坐标系相反
    if (cross1 >= 0 and cross2 >= 0 and cross3 >= 0 and cross4 >= 0) or \
            (cross1 <= 0 and cross2 <= 0 and cross3 <= 0 and cross4 <= 0):
        return True
    else:
        return False


def check_points_in_quadrilaterals(points, quadrilaterals):
    point1, point2 = points
    for quad in quadrilaterals:
        if is_point_in_polygon(point1, quad):
            return True
        if is_point_in_polygon(point2, quad):
            return True
    return False


def extend_to_image_boundary(A_vertices, img_width, img_height, step=1.0, extend_down=True):
    """
    将四边形 A 沿指定边的方向延伸至图像边界。

    Args:
        A_vertices (list): 四边形 A 的四个顶点 [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]。
        img_width (int): 图像宽度。
        img_height (int): 图像高度。
        step (float): 每次延伸的步长。
        extend_down (bool): True=向下延伸，False=向上延伸。

    Returns:
        list: 延伸后的四边形 A 的四个顶点。
    """
    A_poly = Polygon(A_vertices)
    original_A_vertices = np.array(A_vertices)

    # 1. 计算所有边的长度，找出最长的两条边
    edges = []
    for i in range(4):
        p1, p2 = np.array(A_vertices[i]), np.array(A_vertices[(i + 1) % 4])
        edge_length = np.linalg.norm(p2 - p1)
        edges.append((edge_length, p1, p2, i))  # i 是边的索引

    # 按长度排序，获取最长的两条边
    edges.sort(key=lambda x: x[0], reverse=True)
    longest_edges = edges[:2]

    # 2. 计算统一的延伸方向（在图像坐标系中）
    avg_direction = np.array([0.0, 0.0])
    for _, p1, p2, _ in longest_edges:
        if p1[1] > p2[1]:
            edge_vector = p1 - p2
        else:
            edge_vector = p2 - p1
        # 归一化向量
        edge_vector = edge_vector / np.linalg.norm(edge_vector)
        avg_direction += edge_vector

    # 归一化平均方向向量
    avg_direction = avg_direction / np.linalg.norm(avg_direction)

    # 根据extend_direction调整方向（在图像坐标系中）
    if extend_down:  # down
        # 在图像坐标系中，向下意味着y值增加
        if avg_direction[1] < 0:  # 如果当前方向是向上的，翻转它
            avg_direction = -avg_direction
    else:  # 'up'
        # 在图像坐标系中，向上意味着y值减少
        if avg_direction[1] > 0:  # 如果当前方向是向下的，翻转它
            avg_direction = -avg_direction

    # 3. 确定每条最长边需要延伸的顶点（在图像坐标系中）
    vertices_to_extend = []
    for _, p1, p2, _ in longest_edges:
        # 根据延伸方向确定要延伸的顶点
        if extend_down:
            # 向下延伸，选择y坐标较大的顶点
            vertex_to_extend = p2 if p2[1] > p1[1] else p1
        else:  # 'up'
            # 向上延伸，选择y坐标较小的顶点
            vertex_to_extend = p2 if p2[1] < p1[1] else p1

        vertices_to_extend.append(vertex_to_extend)
    # print("边界判断中需要延伸的顶点", vertices_to_extend)

    # 创建一个映射，记录每个延伸顶点对应的原始顶点索引
    vertex_indices = []
    for vertex_to_extend in vertices_to_extend:
        # 找到这个顶点在原始顶点列表中的索引
        for j in range(4):
            if np.allclose(original_A_vertices[j], vertex_to_extend):
                vertex_indices.append(j)
                break
    # print(vertex_indices)

    # 4. 只延伸选定的顶点
    current_vertices = original_A_vertices.copy()
    extended_distance = 0

    # 设置最大延伸距离，防止无限循环
    max_extend = img_height

    # 循环延伸直到任一顶点到达边界或达到最大延伸距离
    while extended_distance < max_extend:
        # 延伸所有选定的顶点
        for i, vertex_idx in enumerate(vertex_indices):
            # 延伸顶点
            current_vertices[vertex_idx] += avg_direction * step
            # 更新vertices_to_extend列表中的顶点
            vertices_to_extend[i] = current_vertices[vertex_idx]

        # 更新总延伸距离
        extended_distance += step

        # 5. 检查延伸的顶点是否到达图像边界
        for vertex_to_extend in vertices_to_extend:
            # print("延伸后的点", vertex_to_extend)
            if is_point_on_image_boundary(vertex_to_extend, img_width, img_height):
                print(f"顶点 {vertex_to_extend} 到达图像边界，返回当前四边形")
                return current_vertices.tolist()

    # 如果达到最大延伸距离仍未到达边界，返回当前状态
    print(f"达到最大延伸距离 {max_extend}，返回当前四边形")
    return current_vertices.tolist()


def is_point_on_image_boundary(point, img_width, img_height, include_corners=True):
    """
    判断点是否在图像边界上（图像坐标系）

    Args:
        point: (x, y) 点坐标（浮点数或整数）
        img_width: 图像宽度（整数）
        img_height: 图像高度（整数）
        include_corners: 是否包含角落点，默认为True

    Returns:
        bool: True表示点在边界上，False表示点在内部或外部
    """
    x, y = point

    # 边界范围定义（考虑像素精度）
    # 图像有效区域：x ∈ [0, img_width), y ∈ [0, img_height)
    # 边界包括：x=0, x=img_width, y=0, y=img_height

    # 处理浮点数精度问题，使用容差判断
    tolerance = 1
    is_on_x_boundary = (abs(x - 0) < tolerance) or (abs(x - img_width) < tolerance)
    is_on_y_boundary = (abs(y - 0) < tolerance) or (abs(y - img_height) < tolerance)

    # 角落点处理（当include_corners为False时，排除角落点）
    if include_corners:
        return is_on_x_boundary or is_on_y_boundary
    else:
        # 排除四个角落点
        is_corner = ((abs(x - 0) < tolerance and abs(y - 0) < tolerance) or
                     (abs(x - img_width) < tolerance and abs(y - 0) < tolerance) or
                     (abs(x - 0) < tolerance and abs(y - img_height) < tolerance) or
                     (abs(x - img_width) < tolerance and abs(y - img_height) < tolerance))
        return (is_on_x_boundary or is_on_y_boundary) and not is_corner


def extend_along_longest_edge(A_vertices, other_polygons, step=2.0, max_extend=5000, extend_direction='down'):
    """
    在图像坐标系中沿四边形 A 的最长两条边的方向延伸其中一个顶点，直到延伸方向上的顶点进入其他四边形或达到最大延伸距离。

    Args:
        A_vertices (list): 四边形 A 的四个顶点 [(x1,y1), (x2,y2), (x3,y3), (x4,y4)].
        other_polygons (list): 其他同类四边形的顶点列表.
        step (float): 每次延伸的步长.
        max_extend (float): 最大延伸距离.
        extend_direction (str): 'down'=向下延伸，'up'=向上延伸.

    Returns:
        new_A_vertices (list): 延伸后的四边形 A 的顶点.
        collided_polygon (list): 碰撞到的其他四边形（若无则返回 None）.
    """
    A_poly = Polygon(A_vertices)
    original_A_vertices = np.array(A_vertices)

    # 1. 计算所有边的长度，找出最长的两条边
    edges = []
    for i in range(4):
        p1, p2 = np.array(A_vertices[i]), np.array(A_vertices[(i + 1) % 4])
        edge_length = np.linalg.norm(p2 - p1)
        edges.append((edge_length, p1, p2, i))  # i 是边的索引

    # 按长度排序，获取最长的两条边
    edges.sort(key=lambda x: x[0], reverse=True)
    longest_edges = edges[:2]

    # 2. 计算统一的延伸方向（在图像坐标系中）
    avg_direction = np.array([0.0, 0.0])
    for _, p1, p2, _ in longest_edges:
        if p1[1] > p2[1]:
            edge_vector = p1 - p2
        else:
            edge_vector = p2 - p1
        # 归一化向量
        edge_vector = edge_vector / np.linalg.norm(edge_vector)
        avg_direction += edge_vector

    # 归一化平均方向向量
    avg_direction = avg_direction / np.linalg.norm(avg_direction)

    # 根据extend_direction调整方向（在图像坐标系中）
    if extend_direction == 'down':
        # 在图像坐标系中，向下意味着y值增加
        if avg_direction[1] < 0:  # 如果当前方向是向上的，翻转它
            avg_direction = -avg_direction
    else:  # 'up'
        # 在图像坐标系中，向上意味着y值减少
        if avg_direction[1] > 0:  # 如果当前方向是向下的，翻转它
            avg_direction = -avg_direction

    # 3. 确定每条最长边需要延伸的顶点（在图像坐标系中）
    vertices_to_extend = []
    for _, p1, p2, _ in longest_edges:
        # 根据延伸方向确定要延伸的顶点
        if extend_direction == 'down':
            # 向下延伸，选择y坐标较大的顶点
            vertex_to_extend = p2 if p2[1] > p1[1] else p1
        else:  # 'up'
            # 向上延伸，选择y坐标较小的顶点
            vertex_to_extend = p2 if p2[1] < p1[1] else p1

        vertices_to_extend.append(vertex_to_extend)
    # print("需要延伸的顶点", vertices_to_extend)

    # 创建一个映射，记录每个延伸顶点对应的原始顶点索引
    vertex_indices = []
    for vertex_to_extend in vertices_to_extend:
        # 找到这个顶点在原始顶点列表中的索引
        for j in range(4):
            if np.allclose(original_A_vertices[j], vertex_to_extend):
                vertex_indices.append(j)
                break
    # print(vertex_indices)
    # 4. 只延伸选定的顶点
    current_vertices = original_A_vertices.copy()
    extended_distance = 0

    for _ in range(int(max_extend / step)):
        # 只延伸选定的顶点
        for i, vertex_idx in enumerate(vertex_indices):
            # 延伸顶点
            current_vertices[vertex_idx] += avg_direction * step
            # 更新vertices_to_extend列表中的顶点
            vertices_to_extend[i] = current_vertices[vertex_idx]

        # 更新总延伸距离
        extended_distance += step

        # 5. 检查延伸的顶点是否进入其他多边形
        for vertex_to_extend in vertices_to_extend:
            for other in other_polygons:
                # print("延伸后的点", vertex_to_extend)
                # print(is_point_in_polygon(vertex_to_extend, other))
                if is_point_in_polygon(vertex_to_extend, other):
                    # print("延伸后的点", vertices_to_extend)
                    return current_vertices.tolist(), other

        # 检查是否达到最大延伸距离
        if extended_distance >= max_extend:
            break

    return current_vertices.tolist(), None


def is_edge_near_boundary(edge, image_width, image_height, threshold=10):
    """
    判断给定边是否靠近图像边界

    参数:
        edge: 边的两个顶点 [[x1, y1], [x2, y2]]
        image_width: 图像宽度
        image_height: 图像高度
        threshold: 判定靠近边界的阈值（默认 10 像素）

    返回:
        bool: 如果边靠近边界返回 True，否则返回 False
    """
    x1, y1 = edge[0]
    x2, y2 = edge[1]

    # 检查是否靠近左边界 (x <= threshold)
    near_left = (x1 <= threshold) and (x2 <= threshold)
    # 检查是否靠近右边界 (x >= image_width - threshold)
    near_right = (x1 >= image_width - threshold) and (x2 >= image_width - threshold)
    # 检查是否靠近上边界 (y <= threshold)
    near_top = (y1 <= threshold) and (y2 <= threshold)
    # 检查是否靠近下边界 (y >= image_height - threshold)
    near_bottom = (y1 >= image_height - threshold) and (y2 >= image_height - threshold)

    # 如果靠近任意一条边界，返回 True
    return near_left or near_right or near_top or near_bottom


def find_shortest_edges_and_classify(vertices):
    """
    输入: 四边形的四个顶点坐标，格式为 [[x1, y1], [x2, y2], [x3, y3], [x4, y4]]
    输出: 返回上端和下端的点坐标，格式为 {"上端": [[x1, y1], [x2, y2]], "下端": [[x3, y3], [x4, y4]]}
    """
    if len(vertices) != 4:
        raise ValueError("必须提供四边形的四个顶点坐标")

    # 计算所有可能的边（组合顶点对，不重复）
    edges = list(itertools.combinations(vertices, 2))

    # 计算每条边的长度，并存储为 (边, 长度, 平均y坐标)
    edge_info = []
    for (p1, p2) in edges:
        x1, y1 = p1
        x2, y2 = p2
        length = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
        avg_y = (y1 + y2) / 2
        edge_info.append((p1, p2, length, avg_y))

    # 按长度排序，取最短的两条边
    edge_info.sort(key=lambda x: x[2])
    shortest_edges = edge_info[:2]

    # 按平均 y 坐标排序，较大的为下端，较小的为上端
    shortest_edges.sort(key=lambda x: x[3])  # 去掉 reverse=True 参数
    upper_edge = [list(shortest_edges[0][0]), list(shortest_edges[0][1])]  # 上端的两点
    lower_edge = [list(shortest_edges[1][0]), list(shortest_edges[1][1])]  # 下端的两点

    return {"up": upper_edge, "down": lower_edge}


def calculate_iou(poly1, poly2):
    """计算两个凸四边形的IOU（交并比）"""
    if not poly1.is_valid or not poly2.is_valid:
        return 0.0
    intersection = poly1.intersection(poly2).area
    union = poly1.union(poly2).area
    return intersection / union if union > 0 else 0


def get_oriented_bounding_box(points):
    """为凸四边形生成最小外接矩形（OBB）"""
    hull = Polygon(points).convex_hull
    return hull.minimum_rotated_rectangle


def get_endpoint_zones(quad, zone_size=10):
    """
    获取凸四边形最短两条边的端点区域
    参数:
        quad: 凸四边形顶点列表 [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]
        zone_size: 区域扩展半径
    返回:
        (upper_zone, lower_zone): 上下端点区域多边形
    """
    # 计算所有边及其长度
    edges = []
    for i in range(4):
        p1, p2 = quad[i], quad[(i + 1) % 4]
        length = np.linalg.norm(np.array(p1) - np.array(p2))
        edges.append((length, p1, p2))

    # 取最短的两条边
    edges.sort(key=lambda x: x[0])
    edge1, edge2 = edges[0], edges[1]

    # 为每条边生成端点区域
    def create_edge_zone(edge):
        p1, p2 = edge[1], edge[2]
        direction = np.array(p2) - np.array(p1)
        if np.linalg.norm(direction) < 1e-6:  # 避免零向量
            return None
        direction = direction / np.linalg.norm(direction)
        perpendicular = np.array([-direction[1], direction[0]])

        # 创建围绕端点的方形区域
        zone_points = [
            p1 + perpendicular * zone_size,
            p1 - perpendicular * zone_size,
            p2 - perpendicular * zone_size,
            p2 + perpendicular * zone_size
        ]
        return Polygon(zone_points).convex_hull

    zone1 = create_edge_zone(edge1)
    zone2 = create_edge_zone(edge2)

    # 确定上下区域（基于Y坐标）
    def determine_upper_lower(edge):
        p1, p2 = edge[1], edge[2]
        return (p1[1] + p2[1]) / 2  # 返回边的平均Y坐标

    # 按Y坐标排序确定上下
    edges_sorted = sorted([edge1, edge2], key=determine_upper_lower)
    upper_zone = create_edge_zone(edges_sorted[0])  # Y值较小的为上端
    lower_zone = create_edge_zone(edges_sorted[1])  # Y值较大的为下端

    return upper_zone, lower_zone


def check_endpoint_iou(target_quad, other_quads, iou_threshold=0.1):
    """
    检查目标四边形的端点区域是否与其他四边形存在IOU
    参数:
        target_quad: 目标四边形顶点列表
        other_quads: 其他四边形列表 [quad1, quad2, ...]
        iou_threshold: IOU阈值
    返回:
        (has_upper_iou, has_lower_iou): 上下端点是否存在重叠
    """
    upper_zone, lower_zone = get_endpoint_zones(target_quad)
    print(upper_zone, lower_zone)

    has_upper = False
    has_lower = False

    for other_quad in other_quads:
        other_poly = Polygon(other_quad)

        if upper_zone and upper_zone.is_valid:
            iou = calculate_iou(upper_zone, other_poly)
            if iou > iou_threshold:
                has_upper = True

        if lower_zone and lower_zone.is_valid:
            iou = calculate_iou(lower_zone, other_poly)
            if iou > iou_threshold:
                has_lower = True

        if has_upper and has_lower:  # 提前终止
            break

    return has_upper, has_lower


def order_points(pts):
    """输入：4个点的坐标 [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]
       输出：按 左上、右上、右下、左下 排序后的点
    """
    pts = np.array(pts)
    # 计算中心点
    center = np.mean(pts, axis=0)

    # 按 y 坐标排序（区分上下）
    sorted_y = pts[np.argsort(pts[:, 1])]
    top = sorted_y[:2]  # 两个上顶点
    bottom = sorted_y[2:]  # 两个下顶点

    # 对上顶点按 x 排序（区分左上、右上）
    top = top[np.argsort(top[:, 0])]
    tl, tr = top[0], top[1]

    # 对下顶点按 x 排序（区分左下、右下）
    bottom = bottom[np.argsort(bottom[:, 0])]
    bl, br = bottom[0], bottom[1]

    return [tl, tr, br, bl]


def yolov8obb_to_json(results, image_path, img_height, img_width, is_dx, is_iou):
    """转换检测结果为JSON格式"""
    json_data = {
        "version": "4.4.5",
        "flags": {},
        "shapes": [],
        "imagePath": os.path.basename(image_path),
        "imageHeight": img_height,
        "imageWidth": img_width
    }

    for result in results:
        if hasattr(result, 'obb'):
            obb = result.obb
            for cls, rbox, conf in zip(obb.cls, obb.xyxyxyxy, obb.conf):
                if conf < 0.3:  # 置信度阈值
                    continue

                label = result.names[int(cls)]
                points = rbox.cpu().numpy().reshape(-1, 2).tolist()

                points_ed = order_points(points)
                list_data = [arr.tolist() for arr in points_ed]
                json_data["shapes"].append({
                    "label": label,
                    "points": list_data,
                    "group_id": 100 if label == "C_CLS" else 99,
                    "shape_type": "polygon",
                    "flags": {}
                })
    # print(json_data)
    # 执行漏检补全
    json_data["shapes"] = fix_missing_boxes(json_data["shapes"], img_height, img_width, is_dx, is_iou)
    return json_data


def visualize_results(image, shapes, save_path=None):
    """可视化检测结果（不同颜色区分原始框和补全框）"""
    color_map = {
        "C_JDX": (54, 162, 235),  # "天蓝色"
        "C_DX": (237, 100, 166),  # "粉红紫"
        "C_DXXJ": (75, 192, 192),  # "绿松石色"
        "C_YC": (255, 159, 64),  # "琥珀橙"
        "C_CLS": (2, 136, 132),  # "深青绿"
        "C_CLSDLJXJ": (255, 206, 86),  # "淡黄色"
        "C_CLSDXXJ": (34, 197, 94),  # "翡翠绿"
        "C_CLSDXXJ_KT": (108, 99, 255),  # "靛蓝色"
        "C_CLSDXXJ_KTLS": (255, 99, 132),  # "浅珊瑚红"
        "C_CLSZXMJS": (85, 85, 85),  # "中灰色"
        "C_CLSZXMJXJ": (131, 56, 236),  # "深紫罗兰"
        "C_CLSZZDC": (25, 130, 196),  # "钢青色"
        "C_DLJX": (6, 182, 212),  # "青绿色"
        "C_DLJXJ": (255, 111, 97),  # "浅红色"
        "C_JCX": (214, 40, 40),  # "鲜红色"
        "C_JCXDLJXJ": (45, 212, 191),  # "青蓝色"
        "C_JCXDXXJ": (153, 102, 255),  # "紫罗蓝"
        "C_JCXZXMJS": (14, 165, 233),  # "亮蓝色"
        "C_JCXZXMJXJ": (59, 130, 246),  # "皇家蓝"
        "C_JCXZXMJXJW": (124, 58, 237),  # "深紫色"
    }
    for shape in shapes:
        label = shape['label']
        points = np.array(shape['points'], dtype=np.int32)

        # 判断是否为补全框（通过group_id或其他特征）
        color = color_map.get(label, (255, 255, 255))
        if 'filled' in shape.get('flags', {}):
            color = color_map.get(f"{label}_filled", color)

        # 绘制多边形
        cv2.polylines(image, [points], True, color, 10)
        # 绘制标签
        cv2.putText(image, label, tuple(points[0]),
                    cv2.FONT_HERSHEY_SIMPLEX, 1, color, 4)

    if save_path:
        cv2.imwrite(save_path, image)
    return image


def count_labels(data, target_labels):
    """统计各类别数量"""
    label_counts = {label: 0 for label in target_labels}

    for shape in data['shapes']:
        label = shape['label']
        if label in label_counts:
            label_counts[label] += 1

    return label_counts


# ---------------------- 主处理流程 ----------------------

def process_images(model, image_dir, json_dir=None, vis_dir=None, imgsz=1024, excel_path=None, is_dx=False,
                   is_iou=False):
    """处理整个图片目录并将结果保存到Excel"""
    if json_dir: os.makedirs(json_dir, exist_ok=True)
    if vis_dir: os.makedirs(vis_dir, exist_ok=True)

    # 用于存储所有图片的检测结果
    all_results = []
    # 用于存储所有可能的标签
    all_labels = set()
    # result_dict = {}
    #  随机打乱抽200张
    # file_list = os.listdir(image_dir)
    # random.shuffle(file_list)
    # selected_files = file_list[:1000]
    # for img_name in selected_files:

    for img_name in os.listdir(image_dir):
        if not img_name.lower().endswith(('.png', 'jpg', '.jpeg')):
            continue

        img_path = os.path.join(image_dir, img_name)

        img = cv2.imread(img_path)
        if img is None:
            print(f"无法读取图片: {img_path}")
            continue
        start = time.time()
        # 执行推理
        results = model(img, imgsz=imgsz, conf=0.25, device=0)

        # 生成JSON结果
        json_data = yolov8obb_to_json(results, img_path, img.shape[0], img.shape[1], is_dx, is_iou)
        # print("json-------------", json_data)
        # for data in json_data['shapes']:
        #     if data['label'] not in result_dict:
        #         result_dict[data['label']] = []
        #     result_dict[data['label']].append(data['points'])
        label_counts = count_labels(json_data, target_labels)
        # 更新所有标签集合
        all_labels.update(label_counts.keys())
        # print(label_counts)
        #  取消日志输出
        # logg_info = f"图片名：{img_name}，检测结果中包含有："
        # for label, count in label_counts.items():
        #     if count != 0:
        #         logg_info += f"类别 {label}: {count} 个，"
        # logging.info(logg_info)

        # 保存JSON
        if json_dir:
            json_path = os.path.join(json_dir, f"{os.path.splitext(img_name)[0]}.json")
            with open(json_path, 'w') as f:
                json.dump(json_data, f, indent=2)

        # 可视化结果
        if vis_dir:
            vis_path = os.path.join(vis_dir, img_name)
            visualize_results(img.copy(), json_data["shapes"], vis_path)

        # 将当前图片的结果添加到列表中
        result_row = {"图片名": img_name}
        result_row.update(label_counts)
        all_results.append(result_row)

        print(f"处理完成: {img_name}, 处理总时间: {time.time() - start}")

    # 如果指定了Excel路径，则保存结果
    if excel_path:
        # 创建DataFrame
        df = pd.DataFrame(all_results)

        # 确保所有标签都有对应的列
        for label in all_labels:
            if label not in df.columns:
                df[label] = 0

        # 调整列顺序，确保"图片名"在第一列
        columns = ["图片名"] + [label for label in all_labels if label != "图片名"]
        df = df[columns]

        # 保存到Excel
        df.to_excel(excel_path, index=False)
        print(f"检测结果已保存到Excel: {excel_path}")

    # return result_dict


if __name__ == "__main__":
    # 初始化模型
    model = YOLO('/media/liuhw/38185FB1185F6CBE/disk_d/best.pt')  # 替换为你的OBB模型路径

    # 设置路径

    image_dir = "/media/liuhw/38185FB1185F6CBE/disk_t/断散股测试/新_承力索电连接线断散股测试数据/3-2.承力索_本体散股-轻微"
    # json_dir = "/home/nbk/twd/yolo-obb/data/obb-xuangua/json"   # 设为None不保存json
    vis_dir = "/media/liuhw/38185FB1185F6CBE/disk_t/断散股测试/新_承力索电连接线断散股测试数据/3-2.承力索_本体散股-轻微/out"  # 设为None不保存可视化
    # excle_path = '/home/nbk/twd/yolo-obb/data/obb-xuangua/豆庄-东戌4142/result.xlsx'  # 设为None不保存表格
    # 运行处理
    # for file in os.listdir(image_dir):
    #     file_dir = os.path.join(image_dir, file)
    #     output_dir = os.path.join(vis_dir, file)
    #     excle_path = os.path.join(output_dir, file)
    process_images(
        model,
        image_dir=image_dir,
        # json_dir=json_dir,
        vis_dir=vis_dir,
        imgsz=1024,  # 推理尺寸
        is_dx=True,  # 是否将dx补全
        is_iou=True,  # 缓解cls和jcx之间的误识别
        # excel_path=excle_path
    )
